/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       ms3dloader.cpp
 * Author:     karooolek
 * Created on: 2009-03-25
 *
 **********************************************************************************************************************/

#include "ms3dloader.h"

namespace mGameEngine
{
namespace Graphics
{

MS3DLoader::MS3DLoader() :
    MeshLoader()
{
}

MS3DLoader::MS3DLoader(const String &filename) :
    MeshLoader(filename)
{
}

MS3DLoader::~MS3DLoader()
{
}

void MS3DLoader::load()
{
    // based on MS3DFile by chUmbaLum sOft
    // http://chumbalum.swissquake.ch/

    // begin mesh loading
    _state = 0.0f;

    // delete old mesh
    if(_mesh)
    {
        delete _mesh;
        _mesh = NULL;
    }

    // open file for reading in binary mode
    FILE *fp = fopen(_filename, "rb");
    if(!fp)
    {
        return;
    }

    // read MS3D header
    MS3DHeader header;
    fread(&header, 1, sizeof(MS3DHeader), fp);

    // invalid header or version
    if(strncmp(header.id, "MS3D000000", 10) != 0 || header.version != 4)
    {
        return;
    }

    // read vertices
    uint16 nNumVertices;
    fread(&nNumVertices, 1, sizeof(uint16), fp);
    Vector<MS3DVertex> arrVertices(nNumVertices);
    fread(&arrVertices[0], nNumVertices, sizeof(MS3DVertex), fp);

    // read triangles
    uint16 nNumTriangles;
    fread(&nNumTriangles, 1, sizeof(uint16), fp);
    Vector<MS3DTriangle> arrTriangles(nNumTriangles);
    fread(&arrTriangles[0], nNumTriangles, sizeof(MS3DTriangle), fp);

    /*
    // read edges
    List<uint32> edgePair;
    for(uint i = 0; i < arrTriangles.size(); i++)
    {
        uint16 a, b;
        a = arrTriangles[i].vertexIndices[0];
        b = arrTriangles[i].vertexIndices[1];
        if(a > b)
        {
            std::swap(a, b);
        }

        edgePair.push_back(b << 16 | a);

        a = arrTriangles[i].vertexIndices[1];
        b = arrTriangles[i].vertexIndices[2];
        if(a > b)
        {
            std::swap(a, b);
        }
        edgePair.push_back(b << 16 | a);

        a = arrTriangles[i].vertexIndices[2];
        b = arrTriangles[i].vertexIndices[0];
        if(a > b)
        {
            std::swap(a, b);
        }
        edgePair.push_back(b << 16 | a);

    }

    edgePair.unique();

    for(List<uint32>::Iterator it = edgePair.begin();
        it != edgePair.end();
        ++it)
    {
        uint EdgePair = *it;
        MS3DEdge Edge;
        Edge.edgeIndices[0] = uint16(EdgePair);
        Edge.edgeIndices[1] = uint16((EdgePair >> 16) & 0xFFFF);
        _arrEdges.push_back(Edge);
    }*/

    // read groups
    uint16 nNumGroups;
    fread(&nNumGroups, 1, sizeof(uint16), fp);
    Vector<MS3DGroup> arrGroups(nNumGroups);
    for(uint i = 0; i < nNumGroups; i++)
    {
        fread(&arrGroups[i].flags, 1, sizeof(uint8), fp);
        fread(&arrGroups[i].name, 32, sizeof(char), fp);
        fread(&arrGroups[i].numtriangles, 1, sizeof(uint16), fp);
        arrGroups[i].triangleIndices
                = new uint16[arrGroups[i].numtriangles];
        fread(arrGroups[i].triangleIndices, arrGroups[i].numtriangles,
                sizeof(uint16), fp);
        fread(&arrGroups[i].materialIndex, 1, sizeof(char), fp);
    }

    // read materials
    uint16 nNumMaterials;
    fread(&nNumMaterials, 1, sizeof(uint16), fp);
    Vector<MS3DMaterial> arrMaterials(nNumMaterials);
    fread(&arrMaterials[0], nNumMaterials, sizeof(MS3DMaterial), fp);

    // read animation
    float animFPS, time;
    int32 frames;
    fread(&animFPS, 1, sizeof(float), fp);
    fread(&time, 1, sizeof(float), fp);
    fread(&frames, 1, sizeof(int32), fp);

    // read joints
    uint16 nNumJoints;
    fread(&nNumJoints, 1, sizeof(uint16), fp);
    Vector<MS3DJoint> arrJoints(nNumJoints);
    for(uint i = 0; i < nNumJoints; i++)
    {
        fread(&arrJoints[i].flags, 1, sizeof(uint8), fp);
        fread(&arrJoints[i].name, 32, sizeof(char), fp);
        fread(&arrJoints[i].parentName, 32, sizeof(char), fp);
        fread(&arrJoints[i].rotation, 3, sizeof(float), fp);
        fread(&arrJoints[i].position, 3, sizeof(float), fp);
        fread(&arrJoints[i].numKeyFramesRot, 1, sizeof(uint16), fp);
        fread(&arrJoints[i].numKeyFramesTrans, 1, sizeof(uint16), fp);
        arrJoints[i].keyFramesRot = new MS3DKeyframeOrientation[arrJoints[i].numKeyFramesRot];
        fread(arrJoints[i].keyFramesRot, arrJoints[i].numKeyFramesRot, sizeof(MS3DKeyframeOrientation), fp);
        arrJoints[i].keyFramesTrans = new MS3DKeyframePosition[arrJoints[i].numKeyFramesTrans];
        fread(arrJoints[i].keyFramesTrans, arrJoints[i].numKeyFramesTrans, sizeof(MS3DKeyframePosition), fp);
    }

    // create materials
    Vector<Material *> materials(nNumMaterials);
    for(uint i = 0; i != nNumMaterials; ++i)
    {
        Material *&material = materials[i];

        // create textured material
        if(strlen(arrMaterials[i].texture))
        {
            String texFile = getFilePath() + arrMaterials[i].texture; // concatenate mesh path and image path
            ImageLoader ld(texFile);
            ld.load();
            Texture *texture = new Texture(ld.getImage(), TEX_WRAP_CLAMP, TEX_FILTER_BILINEAR);
            ld.unload();
            material = new TexturedMaterial(texture,
                                            Color(arrMaterials[i].ambient),
                                            Color(arrMaterials[i].diffuse),
                                            Color(arrMaterials[i].specular),
                                            arrMaterials[i].shininess,
                                            Color(arrMaterials[i].emissive));
        }
        // no textures, standard material
        else
        {
            material = new StandardMaterial(Color(arrMaterials[i].ambient),
                                            Color(arrMaterials[i].diffuse),
                                            Color(arrMaterials[i].specular),
                                            arrMaterials[i].shininess,
                                            Color(arrMaterials[i].emissive));
        }
    }

    // create vertices
    Vector<Vertex> vertices(nNumVertices);
    for(uint i = 0; i != nNumVertices; ++i)
    {
        Vertex &vertex = vertices[i];

        vertex.position = Vector3(arrVertices[i].vertex);
    }

    // create groups
    Vector<Group> groups(nNumGroups);
    for(uint i = 0; i != nNumGroups; ++i)
    {
        Group &group = groups[i];

        group.name = arrGroups[i].name;

        // create group edges

        // create group faces
        group.faces.resize(arrGroups[i].numtriangles);
        for(uint j = 0; j != arrGroups[i].numtriangles; ++j)
        {
            Face &face = group.faces[j];

            // get triangle vertices
            MS3DTriangle &triangle = arrTriangles[arrGroups[i].triangleIndices[j]];
            for(uint k = 0; k != 3; ++k)
            {
                uint16 i = face.vertices[k] = triangle.vertexIndices[k];
                vertices[i].normal = Vector3(triangle.vertexNormals[k]);
                vertices[i].texCoords[0] = Vector2(triangle.s[k], triangle.t[k]);
            }
        }

        group.material = materials[arrGroups[i].materialIndex];
    }

    // create bones
    Vector<Bone> bones(nNumJoints);
    for(uint i = 0; i != nNumJoints; ++i)
    {
        Bone &bone = bones[i];

        bone.name = arrJoints[i].name;

        // TODO
        bone.parent = NULL;

        // bone local transformation
        Quaternion ori(arrJoints[i].rotation[0],
                       arrJoints[i].rotation[1],
                       arrJoints[i].rotation[2]);
        Vector3 pos(arrJoints[i].position);
        bone.local = bone.baseLocal = Matrix4x4(ori, pos);
    }

    // close file
    fclose(fp);

    // create mesh
    _mesh = new Mesh(vertices, groups, bones);
    _mesh->calculateTangents();

    // mesh is loaded
    _state = 1.0f;
}

}
}

